﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace UICommon.ConvertVedio
{
    /// <summary>
    /// 转换视频处理
    /// </summary>
    public class ConvertVedioProcess
    {

        /// <summary>
        /// 视频宽
        /// </summary>
        public String Width { 
            get
            {
                //if(String.IsNullOrEmpty(m_Width))
                //{
                //    return "600";
                //}
                return m_Width;
            }
            set { m_Width = value; }
        }

        /// <summary>
        /// 视频高
        /// </summary>
        public String Height
        {
            get
            {
                //if (String.IsNullOrEmpty(m_Height))
                //{
                //    return "480";
                //}
                return m_Height;
            }
            set { m_Height = value; }
        }

        /// <summary>
        /// 输出信息
        /// </summary>
        public String OutputInfo
        {
            get { return m_OutPutBuilder.ToString(); }
        }

        /// <summary>
        /// 转换是否成功
        /// </summary>
        public Boolean IsSuccessful
        {
            get { return m_IsSuccessful; }
        }

        /// <summary>
        /// 转换进度
        /// </summary>
        public Int16 Process
        {
            get
            {
                if (IsSuccessful)
                    return 100;
                return m_Process;
            }
        }
        
        /// <summary>
        /// 转换
        /// </summary>
        public void Convert(String ffmpegPath, String inputOptions, String fileName, String outputOptions, String targetFileName)
        {
            CheckVideo(fileName);
            StringBuilder argumentsBuilder = new StringBuilder();
            argumentsBuilder.Append(inputOptions); //-threads 4 -i
            argumentsBuilder.Append(" ");
            argumentsBuilder.Append("\"");
            argumentsBuilder.Append(@fileName);//E:\SelfStudy\VideoCode\vido\07.rm
            argumentsBuilder.Append("\"");
            argumentsBuilder.Append(" ");
            argumentsBuilder.Append(outputOptions);//-vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -crf 24 -bt 256k -refs 1 -coder 0 -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -ab 128k -ar 48000 -ac 2 
            argumentsBuilder.Append(" ");
            argumentsBuilder.Append("\"");
            argumentsBuilder.Append(@targetFileName);//first07.mp4
            argumentsBuilder.Append("\"");
            Process p = new Process();//建立外部调用线程
            p.StartInfo.FileName = @ffmpegPath; // @"E:\SelfStudy\VideoCode\ffmpeg\bin\ffmpeg.exe";//要调用外部程序的绝对路径
            p.StartInfo.Arguments = argumentsBuilder.ToString(); // @"-threads 4 -i E:\SelfStudy\VideoCode\vido\07.rm  -vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -crf 24 -bt 256k -refs 1 -coder 0 -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -ab 128k -ar 48000 -ac 2 first07.mp4";//参数(这里就是FFMPEG的参数了)
            p.StartInfo.UseShellExecute = false;//不使用操作系统外壳程序启动线程(一定为FALSE,详细的请看MSDN)
            p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中(这个一定要注意,FFMPEG的所有输出信息,都为错误输出流,用StandardOutput是捕获不到任何消息的...这是我耗费了2个多月得出来的经验...mencoder就是用standardOutput来捕获的)
            p.StartInfo.CreateNoWindow = false;//不创建进程窗口
            p.ErrorDataReceived += new DataReceivedEventHandler(Output);//外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN
            p.Start();//启动线程
            p.BeginErrorReadLine();//开始异步读取
            p.WaitForExit();//阻塞等待进程结束

            p.Close();//关闭进程
            p.Dispose();//释放资源
        }

        private void CheckVideo(string fileName)
        {
            if (!CheckIsSupportVideo(fileName))
            {
                throw new Exception("不支持当前视频的转换!");
            }
        }

        /// <summary>
        /// 检查当前文件是否支持转换
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool CheckIsSupportVideo(String fileName)
        {
            String normalVideoFormat = "avi,mov,asf,wmv,rm,rmvb,mkv,flv,WebM,mp4,mpg,m1v,movie,mp2,mpa,mpe,mpeg,qt,dat";
            int lastdotpos = fileName.LastIndexOf(".");
            String extName = fileName.ToLower().Substring(lastdotpos + 1);
            if (normalVideoFormat.IndexOf(extName) == -1)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// 转成H.264
        /// </summary>
        /// <param name="ffmpegPath"></param>
        /// <param name="fileName"></param>
        /// <param name="targetFileName"></param>
        public void Convert(String ffmpegPath, String fileName, String targetFileName)
        {
            //String inputOptions = "-threads 4 -i";
            //String outputOptions = "-vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -crf 24 -bt 256k -refs 1 -coder 0 -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -ab 128k -ar 48000 -ac 2";
            //String outputOptions = "-vcodec libx264 -pix_fmt yuv420p -vprofile high -preset fast -b:v 500k -maxrate 500k -bufsize 1000k -vf scale=trunc(oh*a/2)*2:480 -threads 0 -acodec libvo_aacenc -b:a 128k";
            String inputOptions = "-y -i";
            StringBuilder outputOptionsBuilder = new StringBuilder(128);
            outputOptionsBuilder.Append("-vcodec libx264");
            if(!String.IsNullOrEmpty(Width) && !String.IsNullOrEmpty(Height))
            {
                outputOptionsBuilder.Append(" -s " + Width + "x" + Height);
            }
            outputOptionsBuilder.Append(" -pix_fmt yuv420p -vprofile high -preset fast -b:v 300k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvo_aacenc -b:a 32k");
            //String outputOptions = "-vcodec libx264 -s " + Width + "x" + Height + " -pix_fmt yuv420p -vprofile high -preset fast -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvo_aacenc -b:a 128k";
            String outputOptions = outputOptionsBuilder.ToString();
            Convert(ffmpegPath, inputOptions, fileName, outputOptions, targetFileName);
        }

        /// <summary>
        /// 转成WebM
        /// </summary>
        /// <param name="ffmpegPath"></param>
        /// <param name="fileName"></param>
        /// <param name="targetFileName"></param>
        public void ConvertToWebM(String ffmpegPath, String fileName, String targetFileName)
        {
            //String inputOptions = "-threads 4 -i";
            //String outputOptions = "-vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -crf 24 -bt 256k -refs 1 -coder 0 -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -ab 128k -ar 48000 -ac 2";
            //String outputOptions = "-vcodec libvpx -quality good -cpu-used 5 -b:v 500k -maxrate 500k -bufsize 1000k -vf scale=trunc(oh*a/2)*2:480 -threads 0 -acodec libvorbis -f webm";
            String inputOptions = "-y -i";
            StringBuilder outputOptionsBuilder = new StringBuilder(128);
            outputOptionsBuilder.Append("-vcodec libvpx");
            if (!String.IsNullOrEmpty(Width) && !String.IsNullOrEmpty(Height))
            {
                outputOptionsBuilder.Append(" -s " + Width + "x" + Height);
            }
            outputOptionsBuilder.Append(" -quality good -cpu-used 5 -b:v 300k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvorbis -f webm");
            
            //String outputOptions = "-vcodec libvpx -s " + Width + "x" + Height + " -quality good -cpu-used 5 -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvorbis -f webm";
            String outputOptions = outputOptionsBuilder.ToString();
            Convert(ffmpegPath, inputOptions, fileName, outputOptions, targetFileName);
        }

        /// <summary>
        /// 转成ogg
        /// </summary>
        public void ConvertToOgg(String ffmpegPath, String fileName, String targetFileName)
        {
            //String inputOptions = "-threads 4 -i";
            //String outputOptions = "-vcodec libx264 -s 480x272 -flags +loop -cmp +chroma -crf 24 -bt 256k -refs 1 -coder 0 -me_range 16 -subq 5 -partitions +parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -ab 128k -ar 48000 -ac 2";
            //String outputOptions = "-vcodec libvpx -quality good -cpu-used 5 -b:v 500k -maxrate 500k -bufsize 1000k -vf scale=trunc(oh*a/2)*2:480 -threads 0 -acodec libvorbis -f webm";
            String inputOptions = "-y -i";
            StringBuilder outputOptionsBuilder = new StringBuilder(128);
            outputOptionsBuilder.Append("-vcodec libvorbis");
            if (!String.IsNullOrEmpty(Width) && !String.IsNullOrEmpty(Height))
            {
                outputOptionsBuilder.Append(" -s " + Width + "x" + Height);
            }
            outputOptionsBuilder.Append(" -quality good -cpu-used 5 -b:v 300k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvorbis -f ogg");

            //String outputOptions = "-vcodec libvorbis -s " + Width + "x" + Height + " -quality good -cpu-used 5 -b:v 500k -maxrate 500k -bufsize 1000k -threads 0 -acodec libvorbis -f webm";
            String outputOptions = outputOptionsBuilder.ToString();
            Convert(ffmpegPath, inputOptions, fileName, outputOptions, targetFileName);
        }

        /// <summary>
        /// 转换视频
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="targetFileName"></param>
        /// <remarks>
        /// 在配置文件中配置ffmpeg.exe路径
        /// </remarks>
        public void Convert(String fileName, String targetFileName)
        {
            String ffmpegPath = System.Configuration.ConfigurationManager.AppSettings["ffmpeg"];
            Convert(ffmpegPath, fileName, targetFileName);
        }

        /// <summary>
        /// 转换视频为WebM
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="targetFileName"></param>
        public void ConvertToWebM(String fileName, String targetFileName)
        {
            String ffmpegPath = System.Configuration.ConfigurationManager.AppSettings["ffmpeg"];
            ConvertToWebM(ffmpegPath, fileName, targetFileName);
        }

        private void Output(object sendProcess, DataReceivedEventArgs output)
        {
            if (!String.IsNullOrEmpty(output.Data))
            {
                //处理方法...
                m_OutPutBuilder.Append(Environment.NewLine + "  " + output.Data);
                //
                string dataLine = output.Data.ToLower().Trim();
                if (dataLine.Contains("duration:")) //获取时长，格式：Duration: 00:02:30.60, start: 0.000000, bitrate: 198 kb/s
                {
                    int durPos = dataLine.IndexOf("duration:", System.StringComparison.Ordinal);
                    int commaPos = dataLine.IndexOf(",", System.StringComparison.Ordinal);
                    m_Duration = dataLine.Substring(durPos + 9, commaPos - durPos - 9).Trim();
                    m_TotalSeconds = CaculateVideoTotalSeconds(m_Duration);
                }
                else if (dataLine.Contains("time=")) //获取当前时间点，格式：frame=  140 fps=0.0 q=28.0 size=      43kB time=00:00:06.64 bitrate=  53.3kbits/s   
                {
                    //Trace.WriteLine("proc_ErrorDataReceived" + e.Data);
                    int timePos = dataLine.IndexOf("time=", System.StringComparison.Ordinal);
                    string tmp = dataLine.Substring(timePos);
                    int bitratePos = tmp.IndexOf(" ", System.StringComparison.Ordinal);
                    string timeStr = tmp.Substring(5, bitratePos - 5).Trim();
                    double currentTime = CaculateVideoTotalSeconds(timeStr);
                    m_Process = System.Convert.ToInt16(currentTime / m_TotalSeconds * 100);
                    m_IsSuccessful = Math.Abs(currentTime - m_TotalSeconds) < 1
                                           ? true
                                           : false;

                    //int percent = (int)((float)(curTime.Ticks - BaseTime) / (float)(_lDuration - BaseTime) * 100); //在Trace里面就可以看到整体进度的百分比
                    //Trace.WriteLine("Percent: " + percent + " _lDuration: " + _lDuration + ", curTime: " + curTime.Ticks);
                }
            }
        }

        private double CaculateVideoTotalSeconds(String timeSpanInfo)
        {
            String[] timeInfo = timeSpanInfo.Split(':');
            if(timeInfo.Length != 3)
                return 0;
            int hours = int.Parse(timeInfo[0]);
            int minutes = int.Parse(timeInfo[1]);
            String secondAndms = timeInfo[2];
            String[] secondAndmss = secondAndms.Split('.');
            int seconds = int.Parse(secondAndmss[0]);
            int ms = 0;
            if(secondAndmss.Length == 2)
            {
                ms = int.Parse(secondAndmss[1]);
            }
            TimeSpan timeSpan = new TimeSpan(0, hours, minutes, seconds, ms);
            return timeSpan.TotalSeconds;
        }

        private StringBuilder m_OutPutBuilder = new StringBuilder();
        private String m_Width = "400";//String.Empty;
        private String m_Height = "300";//String.Empty;
        private String m_Duration = String.Empty;
        private Double m_TotalSeconds = 0;
        private Boolean m_IsSuccessful = false;
        private Int16 m_Process = 0;
    }
}
